专注 dfs 20 年, 粗略的说一说基于 subset 的 🌰.
Article Language: Chinese
Example Language: Java
Reading Time: 17min
Partition an Array into K Equal Sum Subsets
input:
An array, and int number k.output:
Boolean whether the array can be divided into k subsets with equal sum.
- 是否能被等分? 如果 k 能够被数组的总和整除, 则有可能存在这样 k 个子集.
- 找到 k 个总和等于 sum/k 的子集.
- 可以使用子集求和的方法, 或者找到所有满足条件的子集的路径.
- 每个元素只能用一次, 所以需要一个额外的数据结构来记录是否使用过.
It could be nicer if srot the array in descending order
It runs k times looking for a qualified subset, so the worst case would be the most O(k _ n! _ n), where n! is for seaching
String Split
input:
String /char array/etc.output:
List<List
给一个 string(直接用 substring)或者 char array( 可以用 StringBuilder), 把它进行切分, 每一个被切分过的 string 长度不能超过 2.
举个 🌰, “abcd”, 返回的结果是, [[a, b, c, d], [a, b, cd], [a, bc, d], [ab, c, d], [ab, cd]].
PS: 每一个 subset 都是有双引号的, 略…
- 每一个元素只和它后一个元素有关系.
- 每一个分支的状态是它自己或者它和后面一个元素.
- 后面一个元素被选过了之后失去进入下一轮的资格.
The number of element in the input is n. Every element can have at most two branches at each level. Therefore, the time complexity is O(2^n);
The height of the tree would be n as well; thus the space would be the call stack which is O(n);
代码如下
1 | public class SplitString{ |
当然这道题应该有更优化的写法, 这里只考虑 dfs.
Valid Permutation of Parentheses
input:
An int n represents the pair of parentheses.output:
A list of string with all valid permutation of given number pair of parentheses.
- 在每一个位置上, 只有 2 个选择, 左括号还是右括号.
- 放置左右括号的前提条件是什么?
- 如果当前已经有了 n 个左括号, 那么就不能再添加左括号.
- 如果当前左括号小于等于右括号的数量, 那么就不能再添加右括号了.
举个 🌰, input = 3
, output = ["((()))", "(()())", "(())()", "()(())", "()()()"]
At every position, it has two choices, adding left or right parenthese. It has 2 * n positions. Therefore, time compexity is O(2^2n).
The recursive call would have at most 2n frames. Therefore, the space complexity is O(2n).
代码如下:
1 | public class ValidParentheses{ |
K Sum II
input:
An int array with UNIQUE numbers, k elements that can sum up the target, and a target number.output:
List of lists of k intergers that sum up the target.
- 暴力求解, 基本不考虑优化.
- 给定数组中的元素是没有重复的, 所以连 set 也不需要了, 只要保证每次只看 index 后面的元素就可以了.
- 限定条件, 每一个结果里面只能有 k 个元素.
举个 🌰, [1, 2, 3, 4]
, k = 2
, target = 5
. 返回值为, [[1, 4][2, 3]]
, 也就是说, 从每一个 index 出发, 希望它每一次只和它后面的另外一个数字相加, 如果结果与 target 相等, 并且结果里面只有 2 个元素, 那么就返回这个结果.
At each level, i represents the number of i-permutations of n, where i is restricted by k. Therefore, at the each level, it’s looking for C(n, 1), C(n, 2), …, C(n, k). To add them up, 1 + C(n, 1) + … + C(n, k) = (1 + 1)^k = 2^k. If k is equal to the length of input. Then the time complexity is O(2^n).
Since we only consider answer with number of k elements, the frame of the call stack would be k, tops. So it’s O(k).
代码如下:
1 | public class KSum { |
Combination Sum II
input:
An unordered int array with DUPLICATED elements, and a target number.output:
All UNIQUE combinations in the array which are candidate numbers sum to the target.
- 依然暴力求解, 即使考虑优化也要考虑重复元素的问题.
- 首先, 返回结果必须是 unique 的. 其次, 每一个元素都只能使用一次.
- 那么就要解决重复元素能不能被重复选择的问题[笑].
- 和 subsets II 的解法一样. 先排个序, 反正 dfs 的时间复杂度已经是指数级的了, 排个序无伤大雅.
- 排序首先确保了结果的有序性, 有助于辨别是否重复, 其次可以选择 HashSet 或者 index 两种方法去重.
At each subsolution position i, it runs n - i times looking for candidate. There are n possible positions, where n is the length of input. Therefore, the time complexity is O(n!).
Space complexity is O(n) for call stack and using index to deduplicate (without HashSet in each level).
1 | public CombSum { |
Combination of Coins
input:
A target value and a list of the value of each coins.output:
A List of lists of candidates of number of each coins that can add up to the target.
- 每一层的状态取决于当前层的 target 大小. 所以需要一个动态的变量来判断分支数量.
- 我们希望把层数控制在 coin 的数量而不是 target 的大小, 因为如果 coin 的面值包含 1 的话, 那么层数就是 target 的大小, 如果 target 很大, 就会爆栈.
At each node of the recursion tree, it has current target / valueOfCoin states. Overall, the one has the most branches is the coin that has smallest value. The states of the node is whatever the factor is. Suppose that factor is k and the number of coin is n. The time complexity is k^n.
Space is the number of coins.
代码如下:
1 | public class CombCoin{ |
Cartesian Product
input:
A 2D array with different length of column in each row.output:
List of lists of Catesian product such that A×BxC = {(x,y,z)|x∈A ∧ y∈B ∧ z∈C};
- 从每一行的第 0 列开始遍历, 直到最后一行, index == input.length;
- 每一个行的元素长度有可能不一致, 考虑越界问题.
- 这里面 for loop 不再是控制层数而是控制每一层的分支, 所以需要退出条件.
- 举个 🌰, 给定一个二维数组[[1, 2, 3],[4, 5],[6, 7, 8]], 它的层数和 row 相关, 分支最多不超过 column 的最大值.
Suppose the matrix has n rows and m columns. It has at most n levels, and at each level, a node has at most m branches. Thus time complexity is O(m^n);
No extra space is used besides the call stack, which is the number of row, O(n).
代码如下:
1 | public class CarteProduct { |
All Factors Of A Number
input:
An int.output:
A list of list of all candidates that the production is given number.
- 从 2 开始找到 input 的完全平方根的整数为止.
- 需要注意的是, 什么时候继续向下除, 什么时候停止.
- 和前面找 sum 的题差不多, 只是需要注意一下乘除法的细节.
For each number at a level, it has sqrt(n) choices to run (although situations are optimized in the code). The recursive call has the most sqrt(n) times. The time complexity is O(sqrt(n) ^ sqrt(n)). LOGN^FACTOR
Sapces is the call stack which is sqrt of n.
代码如下:
1 | public class AllFactors{ |
感谢@景阳同学的友情支持~笔芯 ❤